package com.um.jdy.platform.device.manger;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.um.jdy.common.base.enums.YesOrNo;
import com.um.jdy.common.charge.entity.po.Order;
import com.um.jdy.common.charge.enums.OrderEnum;
import com.um.jdy.common.charge.service.OrderService;
import com.um.jdy.common.device.code.DeviceCode;
import com.um.jdy.common.device.entity.dto.PileDTO;
import com.um.jdy.common.device.entity.po.*;
import com.um.jdy.common.device.entity.vo.PileVO;
import com.um.jdy.common.device.enums.DeviceEnum;
import com.um.jdy.common.device.service.*;
import com.um.jdy.common.platform.code.PlatformCode;
import com.um.jdy.common.platform.entity.po.AppPackage;
import com.um.jdy.common.platform.entity.po.Merchant;
import com.um.jdy.common.platform.service.AppPackageService;
import com.um.jdy.common.platform.service.MerchantService;
import com.um.jdy.common.commons.properties.HshChargingProperties;
import com.um.jdy.common.system.entity.po.Param;
import com.um.jdy.common.system.service.ParamService;
import com.um.jdy.platform.device.entity.dto.*;
import com.um.jdy.platform.device.entity.vo.PileErrorVO;
import com.um.jdy.platform.device.entity.vo.PileItemVO;
import com.um.jdy.platform.device.entity.vo.PileStoragePageExtraDataVO;
import com.um.jdy.rpc.api.eb.RpcEBXhTCPDeviceService;
import com.um.jdy.rpc.entity.ykc.EVYkcUpgradeParams;
import com.um.jdy.rpc.enums.DeviceTypeEnum;
import com.um.jdy.rpc.ev.confirmation.RemoteRestartConfirmation;
import com.um.jdy.rpc.ev.confirmation.SetPriceModelConfirmation;
import com.um.jdy.rpc.ev.confirmation.UpdateTimeConfirmation;
import com.um.jdy.rpc.ev.enums.ConfirmationStatus;
import com.um.jdy.rpc.ev.enums.RestartExecuteType;
import com.um.jdy.rpc.factory.ServiceFactory;
import com.um.springboot.starter.entity.Page;
import com.um.springboot.starter.exception.ServiceException;
import com.um.springboot.starter.orm.entity.Response;
import com.um.springboot.starter.orm.manager.BasePlusManager;
import com.um.springboot.starter.orm.params.PageParams;
import com.um.springboot.starter.utils.*;
import com.um.springcloud.dubbo.cluster.DeviceKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.validation.Valid;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 管理类：充电桩表
 */
@Service
@Slf4j
public class PileManager extends BasePlusManager<PileService, Pile, PileDTO, PileVO,PilePageDTO, PileListDTO> {

    @Autowired
    StationService stationService;

    @Autowired
    GunService gunService;

    @Autowired
    CodeService pileCodeService;

    @Autowired
    OrderService orderService;

    @Autowired
    MerchantService merchantService;

    @Autowired
    FeeService feeService;

    @Autowired
    PlatformTransactionManager transactionManager;// 引入 (平台)事务管理器，Spring 事务策略的核心。

    @Autowired
    ServiceFactory serviceFactory;

    @Autowired
    AppPackageService appPackageService;

    @Autowired
    PileService pileService;

    @Autowired
    ParamService paramService;

    @Autowired
    HshChargingProperties hshChargingProperties;


    /**
     * 分页查询设备库存
     *
     * @param pageParams
     * @return
     */
    public Response<Page<PileVO>> getPileStoragePage(PageParams pageParams) {
        Page<PileVO> page = new Page<>();
        page.setPageNo(pageParams.getPageNo());
        page.setPageSize(pageParams.getPageSize());
        pageParams.getCondition().put("d.com_id", getComId());

        String sqlName = "get_pile_storage_page";
        String sqlAllName = "get_pile_storage_count";

        page = service.getCMSViewModelPageBySqlName(page, sqlName, sqlAllName, PileVO.class, pageParams.getProperties(), pageParams.getValues(),
                pageParams.getRangeEntitys(), pageParams.getOrderNames(), pageParams.getSortOns());

        AppPackage appPackage = appPackageService.getEntity("com_id", getComId());
        PileStoragePageExtraDataVO pileStoragePageExtraDataVO = new PileStoragePageExtraDataVO();
        pileStoragePageExtraDataVO.setLimitDeviceNum(hshChargingProperties.isLimitDeviceNum());

        if(hshChargingProperties.isLimitDeviceNum()) {
            String deviceType = StringUtil.getTrim(pageParams.getCondition().get("d.device_type"));
            Long count = pileService.getCountBySqlName("get_company_device_count", new Object[]{getComId(), deviceType});

            if (DeviceTypeEnum.EB.name().equals(deviceType)) {
                pileStoragePageExtraDataVO.setDeviceTotalNum(appPackage.getDeviceEbNum());
                pileStoragePageExtraDataVO.setDeviceBalanceNum(appPackage.getDeviceEbNum() - count.intValue());

                if(appPackage.getDeviceEbTime() != null)
                    pileStoragePageExtraDataVO.setDays(DateUtil.daysBetween(new Date(),appPackage.getDeviceEbTime()));
            } else if (DeviceTypeEnum.PB.name().equals(deviceType)) {
                pileStoragePageExtraDataVO.setDeviceTotalNum(appPackage.getDevicePbNum());
                pileStoragePageExtraDataVO.setDeviceBalanceNum(appPackage.getDevicePbNum() - count.intValue());

                if(appPackage.getDevicePbTime() != null)
                    pileStoragePageExtraDataVO.setDays(DateUtil.daysBetween(new Date(),appPackage.getDevicePbTime()));
            } else if (DeviceTypeEnum.EBox.name().equals(deviceType)) {
                pileStoragePageExtraDataVO.setDeviceTotalNum(appPackage.getDeviceEboxNum());
                pileStoragePageExtraDataVO.setDeviceBalanceNum(appPackage.getDeviceEboxNum() - count.intValue());

                if(appPackage.getDeviceEboxTime() != null)
                    pileStoragePageExtraDataVO.setDays(DateUtil.daysBetween(new Date(),appPackage.getDeviceEboxTime()));
            } else if (DeviceTypeEnum.EV.name().equals(deviceType)) {
                pileStoragePageExtraDataVO.setDeviceTotalNum(appPackage.getDeviceEboxNum());
                pileStoragePageExtraDataVO.setDeviceBalanceNum(appPackage.getDeviceEboxNum() - count.intValue());

                if(appPackage.getDeviceEvTime() != null)
                    pileStoragePageExtraDataVO.setDays(DateUtil.daysBetween(new Date(),appPackage.getDeviceEvTime()));
            } else if (DeviceTypeEnum.BSB.name().equals(deviceType)) {
                pileStoragePageExtraDataVO.setDeviceTotalNum(appPackage.getDeviceEboxNum());
                pileStoragePageExtraDataVO.setDeviceBalanceNum(appPackage.getDeviceEboxNum() - count.intValue());

                if(appPackage.getDeviceBsbTime() != null)
                    pileStoragePageExtraDataVO.setDays(DateUtil.daysBetween(new Date(),appPackage.getDeviceBsbTime()));
            }
        }

        page.setExtraData(pileStoragePageExtraDataVO);

        return Response.success(page);
    }

    /**
     * 分页查询待运营设备
     *
     * @param pageParams
     * @return
     */
    public Response<Page<PileItemVO>> getPendingPilePage(PageParams pageParams) {
        Page<PileItemVO> page = new Page<>();
        page.setPageNo(pageParams.getPageNo());
        page.setPageSize(pageParams.getPageSize());
        pageParams.getCondition().put("d.com_id", getComId());

        String sqlName = "get_pending_pile_page";
        String sqlAllName = "get_pending_pile_count";

        page = service.getCMSViewModelPageBySqlName(page, sqlName, sqlAllName, PileItemVO.class, pageParams.getProperties(), pageParams.getValues(),
                pageParams.getRangeEntitys(), pageParams.getOrderNames(), pageParams.getSortOns());

        return Response.success(page);
    }

    /**
     * 分页查询运营设备
     *
     * @param pageParams
     * @return
     */
    public Response<Page<PileItemVO>> getPilePage(PageParams pageParams) {
        Page<PileItemVO> page = new Page<>();
        page.setPageNo(pageParams.getPageNo());
        page.setPageSize(pageParams.getPageSize());
        pageParams.getCondition().put("d.com_id", getComId());

        String sqlName = "get_pile_page";
        String sqlAllName = "get_pile_count";

        page = service.getCMSViewModelPageBySqlName(page, sqlName, sqlAllName, PileItemVO.class, pageParams.getProperties(), pageParams.getValues(),
                pageParams.getRangeEntitys(), pageParams.getOrderNames(), pageParams.getSortOns());

        return Response.success(page);
    }

    @Override
    @Transactional
    public Response<Pile> addEntity(PileDTO dto) {
        Pile pile = UMBeanUtils.copyBean(dto,Pile.class);

        DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
        AppPackage appPackage = appPackageService.getEntity("com_id", getComId());
        Long count = pileService.getCountBySqlName("get_company_device_count", new Object[]{getComId(), dto.getDeviceType()});

        if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.EB.name().equals(dto.getDeviceType()) && count >= appPackage.getDeviceEbNum()) {
            throw new ServiceException("两轮桩插座剩余可添加数量为0！");
        }else if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.PB.name().equals(dto.getDeviceType()) && count >= appPackage.getDevicePbNum()){
            throw new ServiceException("充电宝端口剩余可添加数量为0！");
        }else if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.EBox.name().equals(dto.getDeviceType()) && count >= appPackage.getDeviceEboxNum()){
            throw new ServiceException("充电柜仓口剩余可添加数量为0！");
        }else if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.BSB.name().equals(pile.getDeviceType()) && count >= appPackage.getDeviceBsbNum()){
            throw new ServiceException("换电柜仓口剩余可添加数量为0！");
        }else if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.EV.name().equals(pile.getDeviceType()) && count >= appPackage.getDeviceEvNum()){
            throw new ServiceException("汽车枪枪数剩余可添加数量为0！");
        }

        if (StringUtil.isEmpty(pile.getName())) {
            throw new ServiceException("设备名称不能为空！");
        }

        if (StringUtil.isEmpty(pile.getCode())) {
            throw new ServiceException("设备编码不能为空！");
        }

        if (StringUtil.isEmpty(pile.getImei())) {
            throw new ServiceException("设备IMEI码不能为空！");
        }

        if (StringUtil.isEmpty(pile.getDeviceType())) {
            throw new ServiceException("设备类型不能为空！");
        }

        if (dto.getGunCount() <= 0) {
            throw new ServiceException("端口数不能小于等于0！");
        }

        // 桩号包含汉字
//        if (ReUtil.contains(RegexPool.CHINESES, pile.getCode())) {
//            throw new ServiceException("设备编码包含中文！");
//        }

        if (!Pattern.matches("([0-9A-Za-z])+", pile.getCode())) {
            throw new ServiceException("设备编码只能是字母或数字！");
        }

        if (pile.getCode().length() < 4 || pile.getCode().length() > 32) {
            throw new ServiceException("设备编码长度在4至32个字符！");
        }

        if (ReUtil.contains(RegexPool.CHINESES, pile.getImei())) {
            throw new ServiceException("设备IMEI码包含中文！");
        }

        if (!Pattern.matches("([0-9A-Za-z])+", pile.getImei())) {
            throw new ServiceException("设备IMEI码只能是字母或数字！");
        }

        if(DeviceTypeEnum.EV.name().equals(dto.getDeviceType())){
            if (pile.getImei().length() != 14) {
                throw new ServiceException("设备IMEI长度需要为14个字符！");
            }
        }else {
            if (pile.getImei().length() < 4 || pile.getImei().length() > 32) {
                throw new ServiceException("设备IMEI长度在4至32个字符！");
            }
        }

        Code pileCode = pileCodeService.getEntity("code", pile.getCode());
//        if (pileCode == null) {
//            throw new ServiceException("设备编码不存在！");
//        }

        Pile device = service.getEntity("code", pile.getCode());
        if (device != null) {
            throw new ServiceException("设备编码已存在！");
        }

        device = service.getRow(new String[]{"imei"}, new Object[]{pile.getImei()});
        if (device != null) {
            throw new ServiceException("设备IMEI已存在！");
        }

        defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);//新发起一个事务
        TransactionStatus transactionStatus = transactionManager.getTransaction(defaultTransactionDefinition);// 获得事务状态
        try {

            pile.setCreateTime(new Date());
            pile.setDeviceStatus(DeviceEnum.DeviceStatus.NotActivated.name());//未激活
            service.save(pile);

            if (pileCode != null) {
                pileCode.setStatus(YesOrNo.Yes.name());
                pileCodeService.save(pileCode);
            }

            for (int index = 0; index < pile.getGunCount(); index++) {
                Gun gun = new Gun();
                gun.setDevicePileId(StringUtil.getTrim(pile.getDevicePileId()));
                gun.setPort(index + 1);
                gun.setCreateTime(new Date());
                gunService.save(gun);
            }

            transactionManager.commit(transactionStatus);
        } catch (Exception e) {
            // 手动回滚事务
            transactionManager.rollback(transactionStatus);
            log.error("添加失败，数据：{}，原因：{}", JSONUtil.toJSONString(pile), e.toString());
            throw new ServiceException("添加设备失败");
        }
        return Response.success(pile);
    }

    @Override
    @Transactional
    public Response<Pile> updateEntity(String id, PileDTO dto) {
        Pile entity = this.service.getEntity(id);
        boolean needUpdatePrice = !StringUtil.equals(entity.getDeviceFeeId(),dto.getDeviceFeeId());

        BeanUtils.copyProperties(dto, entity);

        Station station = stationService.getEntity(entity.getDeviceStationId());
        if (ObjectUtil.isNotNull(station)) {
            entity.setPlatformMerchantId(station.getPlatformMerchantId());
        }
        service.update(entity);

        List<Gun> listGun = gunService.getList("device_pile_id", entity.getDevicePileId());
        for (Gun gun : listGun) {
            gun.setComId(entity.getComId());
            gunService.update(gun);
        }

        try {
            if (needUpdatePrice && DeviceTypeEnum.EV.name().equals(entity.getDeviceType()) && StringUtil.equals(entity.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name())) {
                service.remoteUpdatePrice(entity);
            }
        }catch (Exception ex){
            log.error("更价出错：{}",ex.getMessage());
        }

        return Response.success(entity);

    }

    /**
     * 下发至运营商
     *
     * @param deliveryDTO
     */
    @Transactional
    public void arrangeToMerchant(DeliveryDTO deliveryDTO) {
        List<PileDTO> piles = deliveryDTO.getPiles();
        String platformMerchantId = StringUtil.getTrim(deliveryDTO.getId());

        Merchant merchant = merchantService.getEntity(platformMerchantId);
        if (null == merchant) {
            throw new ServiceException(PlatformCode.MerchantNotExist);
        }

        piles.stream().forEach(pileDTO -> {
            Pile pile = service.getEntity(new String[]{"code"}, new Object[]{pileDTO.getCode()});
            if (null == pile) {
                throw new ServiceException(DeviceCode.NoPile);
            }

            /*
            if (StrUtil.isNotEmpty(pile.getPlatformMerchantId()) &&
                    null != merchantService.getEntity(pile.getPlatformMerchantId())) {
                throw new ServiceException(DeviceCode.ArrangedMerchant);
            }*/

            pile.setDeliveryTime(new Date());
            pile.setPlatformMerchantId(platformMerchantId);
            pile.setDeviceStationId("");
            service.save(pile);

        });
    }

    /**
     * 下发站场
     *
     * @param deliveryDTO
     */
    @Transactional
    public void arrangeToStation(DeliveryDTO deliveryDTO) {
        List<PileDTO> piles = deliveryDTO.getPiles();
        String deviceStationId = deliveryDTO.getId();

        String deviceType = piles.get(0).getDeviceType();

        Station station = stationService.getEntity(new String[]{"device_station_id"},
                new Object[]{deviceStationId});
        if (null == station) {
            throw new ServiceException(DeviceCode.NoStation);
        }

        Fee fee = feeService.getEntity(new String[]{"device_fee_id"},
                new Object[]{deliveryDTO.getDeviceFeeId()});
        if (null == fee) {
            throw new ServiceException(DeviceCode.NoFee);
        }

        Merchant merchant = merchantService.getEntity(station.getPlatformMerchantId());
        if (null == merchant) {
            throw new ServiceException(PlatformCode.MerchantNotExist);
        }

        piles.stream().forEach(pileDTO -> {
            if(!pileDTO.getDeviceType().equals(deviceType)){
                throw new ServiceException(DeviceCode.NoSupportDifferent);
            }

            Pile pile = service.getEntity(new String[]{"code"}, new Object[]{pileDTO.getCode()});
            if (null == pile) {
                throw new ServiceException(DeviceCode.NoPile);
            }

            /*
            if (StrUtil.isNotEmpty(pile.getDeviceStationId()) &&
                    null != stationService.getEntity(pile.getDeviceStationId())) {
                throw new ServiceException(DeviceCode.ArrangedStation);
            }*/

            pile.setDeviceStationId(deviceStationId);
            pile.setPlatformMerchantId(station.getPlatformMerchantId());
            pile.setDeviceFeeId(deliveryDTO.getDeviceFeeId());
            service.save(pile);

            try {
                if (DeviceTypeEnum.EV.name().equals(pile.getDeviceType()) && StringUtil.equals(pile.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name())) {
                    service.remoteUpdatePrice(pile);
                }
            }catch (Exception ex){
                log.error("更价出错：{}",ex.getMessage());
            }
        });
    }


    /**
     * 设备注册
     *
     * @param devicePileId
     */
    public void register(String devicePileId) {
        Pile pile = service.getEntity(new String[]{"device_pile_id"}, new Object[]{devicePileId});
        if (null == pile) {
            throw new ServiceException(DeviceCode.NoPile);
        }

        if (pile.getDeviceStationId().isEmpty()) {
            throw new ServiceException(DeviceCode.PileUnSend);
        }

        // TODO: 2023-06-06
        //先删除再注册
        //serviceFactory.unregisterDevice(pile.getDeviceType(),pile.getProtocol(),pile.getImei());

        //serviceFactory.registerDevice(pile.getDeviceType(),pile.getProtocol(),pile.getCode(),pile.getImei());
    }

    /**
     * 设备换码
     *
     * @param changeCodeDTO
     */
    public void changeCode(ChangeCodeDTO changeCodeDTO) {
        Pile pile = service.getEntity(new String[]{"device_pile_id"}, new Object[]{changeCodeDTO.getId()});
        if (null == pile) {
            throw new ServiceException(DeviceCode.NoPile);
        }

        if (pile.getDeviceStationId().isEmpty()) {
            throw new ServiceException(DeviceCode.PileUnSend);
        }

        if (!Pattern.matches("([0-9A-Za-z])+", changeCodeDTO.getImei())) {
            throw new ServiceException("设备IMEI码只能是字母或数字！");
        }

        if(service.getRow("imei",changeCodeDTO.getImei()) != null){
            throw new ServiceException(DeviceCode.PileImeiExist);
        }



        if (orderService.getEntity(new String[]{"device_code", "status"}, new Object[]{pile.getCode(), OrderEnum.Status.Charging.name()}) != null) {
            throw new ServiceException(DeviceCode.ChargingNoExchangeImei);
        }

        if(StringUtil.equals(pile.getDeviceType(),DeviceTypeEnum.EV.name())){
            if (orderService.getViewModel("get_ev_place_order", Order.class,new Object[]{pile.getCode(), OrderEnum.PlaceStatus.Place.name(),getComId()}) != null) {
                throw new ServiceException(DeviceCode.PlaceNoExchangeImei);
            }
        }

        //先删除再注册
        //serviceFactory.unregisterDevice(pile.getDeviceType(),pile.getProtocol(),pile.getImei());
        //serviceFactory.registerDevice(pile.getDeviceType(),pile.getProtocol(),pile.getCode(),changeCodeDTO.getImei());

        pile.setImei(changeCodeDTO.getImei());
        service.updateEntity(pile);
    }

    /**
     * 重启设备
     *
     * @param code
     */
    public void restart(String code) {
        Pile pile = service.getEntity("code", code);
        if (null == pile) {
            throw new ServiceException(DeviceCode.NoPile);
        }

        if(!pile.getDeviceStatus().equals(DeviceEnum.DeviceStatus.Online.name())){
            throw new ServiceException(DeviceCode.DeviceOffline);
        }

        if (pile.getDeviceType().equals(DeviceTypeEnum.EB.getKey())){
            serviceFactory.getRpcEBDeviceService(pile.getProtocol()).reqRestart(IdUtils.getPrimaryKey(),new DeviceKey(pile.getImei()));
        }else if (pile.getDeviceType().equals(DeviceTypeEnum.PB.getKey())){
            serviceFactory.getRpcPBDeviceService(pile.getProtocol()).reqRestart(IdUtils.getPrimaryKey(),new DeviceKey(pile.getImei()));
        }else if (pile.getDeviceType().equals(DeviceTypeEnum.EBox.getKey())){
            serviceFactory.getRpcEBoxDeviceService(pile.getProtocol()).reqRestart(IdUtils.getPrimaryKey(),new DeviceKey(pile.getImei()));
        }else if (pile.getDeviceType().equals(DeviceTypeEnum.EV.getKey())){
            serviceFactory.getRpcEVDeviceService(pile.getProtocol()).restart(new DeviceKey(pile.getImei()), RestartExecuteType.Now);
        }


    }

    /**
     * 设备回收
     *
     * @param code
     */
    @Transactional
    public void recover(String code) {
        Pile pile = service.getEntity("code",code);
        if (null == pile) {
            throw new ServiceException(DeviceCode.NoPile);
        }


        if (orderService.getEntity(new String[]{"device_code", "status"}, new Object[]{pile.getCode(), OrderEnum.Status.Charging.name()}) != null) {
            throw new ServiceException(DeviceCode.ChargingNoMoveAndRecycle);
        }

        if(StringUtil.equals(pile.getDeviceType(),DeviceTypeEnum.EV.name())){
            if (orderService.getViewModel("get_ev_place_order", Order.class,new Object[]{pile.getCode(), OrderEnum.PlaceStatus.Place.name(),getComId()}) != null) {
                throw new ServiceException(DeviceCode.PlaceNoMoveAndRecycle);
            }
        }

        Pile newPile = BeanUtil.copyProperties(pile, Pile.class, "devicePileId", "deviceStationId", "platformMerchantId", "deviceFeeId");

        boolean result = service.delete(pile.getDevicePileId());

        newPile.setRecycleTime(new Date());
        result = result && service.add(newPile);

        result = result && gunService.delete("device_pile_id", pile.getDevicePileId());
        for (int index = 0; index < newPile.getGunCount(); index++) {
            Gun gun = new Gun();
            gun.setDevicePileId(newPile.getDevicePileId());
            gun.setPort(index + 1);
            gun.setCreateTime(new Date());
            result = result && gunService.save(gun);
        }

        if(!result){
            throw new ServiceException(DeviceCode.RecycleFailure);
        }
    }

    /**
     * 设备批量回收
     *
     * @param codes
     */
    @Transactional
    public void recoverAll(List<String> codes) {
        for(String code : codes) {
            Pile pile = service.getEntity("code", code);

            if (null == pile) {
                throw new ServiceException(DeviceCode.NoPile);
            }

            if (orderService.getEntity(new String[]{"device_code", "status"}, new Object[]{pile.getCode(), OrderEnum.Status.Charging.name()}) != null) {
                throw new ServiceException(DeviceCode.ChargingNoMoveAndRecycle);
            }

            if (StringUtil.equals(pile.getDeviceType(), DeviceTypeEnum.EV.name())) {
                if (orderService.getViewModel("get_ev_place_order", Order.class, new Object[]{pile.getCode(), OrderEnum.PlaceStatus.Place.name(), getComId()}) != null) {
                    throw new ServiceException(DeviceCode.PlaceNoMoveAndRecycle);
                }
            }

            Pile newPile = BeanUtil.copyProperties(pile, Pile.class, "devicePileId", "deviceStationId", "platformMerchantId", "deviceFeeId");

            boolean result = service.delete(pile.getDevicePileId());

            newPile.setRecycleTime(new Date());
            result = result && service.add(newPile);

            result = result && gunService.delete("device_pile_id", pile.getDevicePileId());
            for (int index = 0; index < newPile.getGunCount(); index++) {
                Gun gun = new Gun();
                gun.setDevicePileId(newPile.getDevicePileId());
                gun.setPort(index + 1);
                gun.setCreateTime(new Date());
                result = result && gunService.save(gun);
            }

            if(!result){
                throw new ServiceException(DeviceCode.RecycleFailure);
            }
        }
    }

    /**
     * 设备转移
     *
     * @param transferDTO
     */
    @Transactional
    public void transfer(TransferDTO transferDTO) {
        for(String code : transferDTO.getCodes()) {
            Pile pile = service.getEntity(new String[]{"code"}, new Object[]{code});
            if (null == pile) {
                throw new ServiceException(DeviceCode.NoPile);
            }


            if (orderService.getEntity(new String[]{"device_code", "status"}, new Object[]{pile.getCode(), OrderEnum.Status.Charging.name()}) != null) {
                throw new ServiceException(DeviceCode.ChargingNoMoveAndRecycle);
            }

            if (StringUtil.equals(pile.getDeviceType(), DeviceTypeEnum.EV.name())) {
                if (orderService.getViewModel("get_ev_place_order", Order.class, new Object[]{pile.getCode(), OrderEnum.PlaceStatus.Place.name(), getComId()}) != null) {
                    throw new ServiceException(DeviceCode.PlaceNoMoveAndRecycle);
                }
            }

            Pile newPile = BeanUtil.copyProperties(pile, Pile.class, "devicePileId", "deviceStationId", "deviceFeeId");

            Station station = stationService.getEntity(transferDTO.getStationId());
            if (null == station) {
                throw new ServiceException(DeviceCode.NoStation);
            }

            newPile.setDeviceStationId(station.getDeviceStationId());
            newPile.setDeviceFeeId(transferDTO.getFeeId());
            boolean result = service.save(newPile);

            result = result && service.delete(pile.getDevicePileId());

            result = result && gunService.delete("device_pile_id", pile.getDevicePileId());
            for (int index = 0; index < newPile.getGunCount(); index++) {
                Gun gun = new Gun();
                gun.setDevicePileId(newPile.getDevicePileId());
                gun.setPort(index + 1);
                gun.setCreateTime(new Date());
                result = result && gunService.save(gun);
            }

            if(!result){
                throw new ServiceException(DeviceCode.TransferFailure);
            }

            if(StringUtil.equals(pile.getDeviceType(),DeviceTypeEnum.EV.name())){
                if (StringUtil.equals(pile.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name()) &&
                        !StringUtil.equals(pile.getDeviceFeeId(),transferDTO.getFeeId())) {
                    try {
                        service.remoteUpdatePrice(newPile);
                    }catch (Exception ex){
                        log.error("移动设备更新计费模型失败：{}",ex.getMessage());
                    }
                }
            }
        }
    }


    /**
     * 批量更换运营商
     * @param exchangeDTO
     * @return
     */
    @Transactional
    public void exchange(ExchangeDTO exchangeDTO) {
        for(String code : exchangeDTO.getCodes()) {
            Pile pile = service.getEntity(new String[]{"code"}, new Object[]{code});
            if (null == pile) {
                throw new ServiceException(DeviceCode.NoPile);
            }


            Pile newPile = BeanUtil.copyProperties(pile, Pile.class, "platformMerchantId","devicePileId", "deviceStationId");

            if (orderService.getEntity(new String[]{"device_code", "status"}, new Object[]{pile.getCode(), OrderEnum.Status.Charging.name()}) != null) {
                throw new ServiceException(DeviceCode.ChargingNoMoveAndRecycle);
            }

            if (StringUtil.equals(pile.getDeviceType(), DeviceTypeEnum.EV.name())) {
                if (orderService.getViewModel("get_ev_place_order", Order.class, new Object[]{pile.getCode(), OrderEnum.PlaceStatus.Place.name(), getComId()}) != null) {
                    throw new ServiceException(DeviceCode.PlaceNoMoveAndRecycle);
                }
            }

            Station station = stationService.getEntity(exchangeDTO.getStationId());
            if (null == station) {
                throw new ServiceException(DeviceCode.NoStation);
            }
            newPile.setComId(station.getComId());
            newPile.setPlatformMerchantId(exchangeDTO.getMerchantId());
            newPile.setDeviceStationId(station.getDeviceStationId());
            newPile.setDeviceFeeId(exchangeDTO.getFeeId());

            boolean result = service.delete(pile.getDevicePileId());

            newPile.setRecycleTime(new Date());
            result = result && service.add(newPile);

            result = result && gunService.delete("device_pile_id", pile.getDevicePileId());
            for (int index = 0; index < newPile.getGunCount(); index++) {
                Gun gun = new Gun();
                gun.setDevicePileId(newPile.getDevicePileId());
                gun.setPort(index + 1);
                gun.setCreateTime(new Date());
                result = result && gunService.save(gun);
            }

            if(!result){
                throw new ServiceException(DeviceCode.ExchangeFailure);
            }

            if(StringUtil.equals(newPile.getDeviceType(),DeviceTypeEnum.EV.name())){
                if (StringUtil.equals(newPile.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name()) &&
                        !StringUtil.equals(pile.getDeviceFeeId(),exchangeDTO.getFeeId())) {

                    service.remoteUpdatePrice(newPile);
                }
            }
        }
    }

    /**
     * 导入excel
     *
     * @param params
     * @return
     */
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public Response<List<PileErrorVO>> importExcel(ImportPileParams params) {
        ExcelReader reader = ExcelUtil.getReader(FileUtil.getInputStream(service.getFilePath(params.getPath())));
        List<Map<String, Object>> list = reader.readAll();

        if(list.size() == 0){
            return Response.failed("导入失败：没有可以导入的数据！");
        }

        List<Param> paramList = paramService.getList("path","station.gun_num","sort_no","asc");
        List<Integer> supportPorts = new ArrayList<>();
        for(Param param : paramList){
            supportPorts.add(StringUtil.toInt(param.getPKey()));
        }

        AppPackage appPackage = appPackageService.getEntity("com_id", getComId());
        List<PileErrorVO> errorList = new ArrayList<>();
        DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();

        for (Map item : list) {
            Pile pile = new Pile();
            pile.setName(StringUtil.getTrim(item.get("设备名称")));
            pile.setCode(StringUtil.getTrim(item.get("设备编码")));
            pile.setImei(StringUtil.getTrim(item.get("设备IMEI码")));
            pile.setDeviceType(params.getDeviceType());

            if(!params.getDeviceType().equals(DeviceTypeEnum.EV.name())) {
                pile.setGunCount(StringUtil.toInt(item.get("端口数")));
            }else{
                pile.setGunCount(StringUtil.toInt(item.get("枪口数")));
                pile.setEvType(StringUtil.getTrim(item.get("交流/直流")));
                pile.setPower(StringUtil.toInt(item.get("工作功率")));
                pile.setVoltage(StringUtil.toInt(item.get("工作电压")));
                pile.setCurrent(StringUtil.toInt(item.get("工作电流")));
            }

            if (StringUtil.isEmpty(pile.getName())) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备名称不能为空！"));
                continue;
            }

            if (StringUtil.isEmpty(pile.getCode())) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备编码不能为空！"));
                continue;
            }

            if (StringUtil.isEmpty(pile.getImei())) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备IMEI码不能为空！"));
                continue;
            }

            if(supportPorts.indexOf(pile.getGunCount()) < 0){
                String str = "";
                for(int index = 0;index < supportPorts.size();index ++){
                    str +=  supportPorts.get(index)+"";
                    if(index < supportPorts.size() - 1)
                        str += ",";
                }
                errorList.add(new PileErrorVO(pile.getCode(), "端口数仅支持:"+str));
                continue;
            }

            if (StringUtil.isEmpty(pile.getDeviceType())) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备类型不能为空！"));
                continue;
            }

            if (pile.getGunCount() <= 0) {
                errorList.add(new PileErrorVO(pile.getCode(), "端口数不能小于等于0！"));
                continue;
            }

            if (pile.getName().length() > 32) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备名称长度在4至32个字符！"));
                continue;
            }

            // 桩号包含汉字
//            if (ReUtil.contains(RegexPool.CHINESES, pile.getCode())) {
//                errorList.add(new PileErrorVO(pile.getCode(), "设备编码包含中文！"));
//                continue;
//            }

            if (!Pattern.matches("([0-9A-Za-z])+", pile.getCode())) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备编码只能是字母或数字！"));
                continue;
            }

            if (pile.getCode().length() < 4 || pile.getCode().length() > 32) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备编码长度在4至32个字符！"));
                continue;
            }

            if (!Pattern.matches("([0-9A-Za-z])+", pile.getImei())) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备IMEI只能是字母或数字！"));
                continue;
            }


            if(!params.getDeviceType().equals(DeviceTypeEnum.EV.name())) {
                if (pile.getImei().length() < 4 || pile.getImei().length() > 32) {
                    errorList.add(new PileErrorVO(pile.getCode(), "设备IMEI长度在4至32个字符！"));
                    continue;
                }

                Long count = pileService.getCountBySqlName("get_company_device_count", new Object[]{getComId(), pile.getDeviceType()});
                if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.EB.name().equals(pile.getDeviceType()) && count >= appPackage.getDeviceEbNum()) {
                    throw new ServiceException("两轮桩插座剩余可添加数量为0！");
                }else if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.PB.name().equals(pile.getDeviceType()) && count >= appPackage.getDevicePbNum()){
                    throw new ServiceException("充电宝端口剩余可添加数量为0！");
                }else if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.EBox.name().equals(pile.getDeviceType()) && count >= appPackage.getDeviceEboxNum()){
                    throw new ServiceException("充电柜仓口剩余可添加数量为0！");
                }else if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.BSB.name().equals(pile.getDeviceType()) && count >= appPackage.getDeviceBsbNum()){
                    throw new ServiceException("换电柜仓口剩余可添加数量为0！");
                }
            }else{
                if (pile.getImei().length() != 14) {
                    errorList.add(new PileErrorVO(pile.getCode(), "设备IMEI长度需要为14个字符！"));
                    continue;
                }

                if(!(StringUtil.equals(DeviceEnum.AcAndDc.AC.name(),pile.getEvType()) || StringUtil.equals(DeviceEnum.AcAndDc.DC.name(),pile.getEvType()))){
                    errorList.add(new PileErrorVO(pile.getCode(), "充电桩交流/直流只能填写AC或DC！"));
                    continue;
                }

                if(StringUtil.equals(DeviceEnum.AcAndDc.AC.name(),pile.getEvType()) && pile.getGunCount() > 1){
                    errorList.add(new PileErrorVO(pile.getCode(), "交流充电桩枪口数只能为1！"));
                    continue;
                }

                if(StringUtil.isEmpty(item.get("工作功率"))){
                    errorList.add(new PileErrorVO(pile.getCode(), "功率不能为空！"));
                    continue;
                }

                if(pile.getVoltage() > 2000){
                    errorList.add(new PileErrorVO(pile.getCode(), "电压不能大于2000！"));
                    continue;
                }

                if(pile.getCurrent() > 2000){
                    errorList.add(new PileErrorVO(pile.getCode(), "电流不能大于2000！"));
                    continue;
                }


                if(StringUtil.equals(DeviceEnum.AcAndDc.AC.name(),pile.getEvType()) && pile.getGunCount() > 1){
                    errorList.add(new PileErrorVO(pile.getCode(), "交流充电桩枪口数只能为1！"));
                    continue;
                }


                Long count = pileService.getCountBySqlName("get_company_device_count", new Object[]{getComId(), pile.getDeviceType()});
                if(hshChargingProperties.isLimitDeviceNum() && DeviceTypeEnum.EV.name().equals(pile.getDeviceType()) && count >= appPackage.getDeviceEvNum()) {
                    throw new ServiceException("汽车桩枪数剩余可添加数量为0！");
                }
            }



            Code pileCode = pileCodeService.getEntity(new String[]{"code"}, new String[]{pile.getCode()});
//            if (pileCode == null) {
//                errorList.add(new PileErrorVO(pile.getCode(), "设备编码不存在！"));
//                continue;
//            }

            Pile device = service.getEntity(new String[]{"code", "is_delete"}, new String[]{pile.getCode(), "0"});
            if (device != null) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备编码已存在！"));
                continue;
            }

            device = service.getRow(new String[]{"imei"}, new Object[]{pile.getImei()});
            if (device != null) {
                errorList.add(new PileErrorVO(pile.getCode(), "设备IMEI已存在！"));
                continue;
            }

            //TODO 暂时不验证
//            Long count = pileService.getCountBySqlName("get_company_pile_count", new Object[]{});
//            if(count >= appPackage.getDeviceNum()){
//                errorList.add(new PileErrorVO(pile.getCode(), "剩余可添加设备数量为0！"));
//                continue;
//            }

            defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);//新发起一个事务
            TransactionStatus transactionStatus = transactionManager.getTransaction(defaultTransactionDefinition);// 获得事务状态
            try {

                pile.setCreateTime(new Date());
                pile.setDeviceStatus(DeviceEnum.DeviceStatus.NotActivated.name());//未激活
                service.save(pile);

                if (pileCode != null) {
                    pileCode.setStatus(YesOrNo.Yes.name());
                    pileCodeService.save(pileCode);
                }

                for (int index = 0; index < pile.getGunCount(); index++) {
                    Gun gun = new Gun();
                    gun.setDevicePileId(StringUtil.getTrim(pile.getDevicePileId()));
                    gun.setPort(index + 1);
                    gun.setCreateTime(new Date());
                    gunService.save(gun);
                }

                transactionManager.commit(transactionStatus);
//                }
            } catch (Exception e) {
                errorList.add(new PileErrorVO(pile.getCode(), "error"));
                log.error("导入桩失败，数据：{}，原因：{}", JSONUtil.toJSONString(item), e.toString());
                // 手动回滚事务
                transactionManager.rollback(transactionStatus);
            }
        }
        return Response.success(errorList);
    }


    public Response upgrade(@RequestBody UpgradeParams params){
        Pile entity = service.getEntity("code",params.getCode());
        if (entity == null) {
            return Response.failed(DeviceCode.NoPile);
        }

        if (!StringUtil.equals(entity.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name())) {
            return Response.failed(DeviceCode.DeviceOffline);
        }

        EVYkcUpgradeParams upgradeParams = UMBeanUtils.copyBean(params,EVYkcUpgradeParams.class);
        serviceFactory.getRpcEVDeviceService(entity.getProtocol()).upgrade(new DeviceKey(entity.getImei()),upgradeParams);
        return Response.success();
    }

    /**
     * cmd_58
     * 设置计费模型
     * @param code
     */
    public Response setPriceModel(String code){
        Pile entity = service.getEntity("code",code);
        if (entity == null) {
            return Response.failed(DeviceCode.NoPile);
        }

        if (!StringUtil.equals(entity.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name())) {
            return Response.failed(DeviceCode.DeviceOffline);
        }

        SetPriceModelConfirmation confirmation = serviceFactory.getRpcEVDeviceService(entity.getProtocol()).setPriceModel(new DeviceKey(entity.getImei()));

        if(confirmation.getStatus() == ConfirmationStatus.Accepted){
            return Response.success();
        }else{
            return Response.failed(DeviceCode.UpdatePriceModelFailure);
        }
    }


    public Response updateTime(String code){
        Pile entity = service.getEntity("code",code);
        if (entity == null) {
            return Response.failed(DeviceCode.NoPile);
        }

        if (!StringUtil.equals(entity.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name())) {
            return Response.failed(DeviceCode.DeviceOffline);
        }
        Date currentTime = new Date();
        UpdateTimeConfirmation confirmation = serviceFactory.getRpcEVDeviceService(entity.getProtocol()).updateTime(new DeviceKey(entity.getImei()),currentTime);

        if(confirmation.getStatus() == ConfirmationStatus.Accepted){
            return Response.success();
        }else{
            return Response.failed(DeviceCode.UpdateTimeFailure);
        }
    }

    /**
     * 重启设备
     *
     * @param params 充电桩编码
     * @return
     */
    public Response reset(RestartParams params) {
        Pile entity = service.getEntity("code",params.getCode());
        if (entity == null) {
            return Response.failed(DeviceCode.NoPile);
        }

        if (!StringUtil.equals(entity.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name())) {
            return Response.failed(DeviceCode.DeviceOffline);
        }

        RestartExecuteType executeType = null;
        if(params.getExeType() == 1)
            executeType = RestartExecuteType.Now;
        else
            executeType = RestartExecuteType.Free;

        RemoteRestartConfirmation confirmation = serviceFactory.getRpcEVDeviceService(entity.getProtocol()).restart(new DeviceKey(entity.getImei()),executeType);

        if(confirmation.getStatus() == ConfirmationStatus.Accepted){
            return Response.success();
        }else{
            return Response.failed(DeviceCode.RestartFailure);
        }

    }

    /**
     * 重启设备
     *
     * @param params 充电桩编码
     * @return
     */
    public Response sendCmd(SendCmdParams params) {
        Pile entity = service.getEntity("code",params.getCode());
        if (entity == null) {
            return Response.failed(DeviceCode.NoPile);
        }

        if (!StringUtil.equals(entity.getDeviceStatus(),DeviceEnum.DeviceStatus.Online.name())) {
            return Response.failed(DeviceCode.DeviceOffline);
        }

        RpcEBXhTCPDeviceService deviceService = (RpcEBXhTCPDeviceService)serviceFactory.getRpcEBDeviceService(entity.getProtocol());
        if(deviceService == null)
            return Response.failed(DeviceCode.SendCmdFailure);

        String result = deviceService.reqSendCommand(OrderUtil.getOrderNo(),new DeviceKey(entity.getImei()),params.getJson());

        return StringUtil.isEmpty(result)?Response.success():Response.failed(DeviceCode.SendCmdFailure);
    }
}
